import JSEncrypt from "jsencrypt"
import CryptoJS from "crypto-js"

import { JsonFormatter } from "./JsonFormatter"
import { getItem, setItem, removeItem, clear } from "./storage"
import * as constant from "./constant"
import { OrderItem } from "../typing"

let jsEncrypt: JSEncrypt;

function cleanStorage(){
  removeItem(constant.KEY_PERMITS)
  removeItem(constant.KEY_TOKEN)
  removeItem(constant.KEY_CRYPTO)
  removeItem(constant.KEY_IV)
  removeItem(constant.KEY_PUBLIC)
}

function handleError(error: any){
  console.log("error:", error);
  cleanStorage()
  window.location.href = window.location.href
}

function sign(params:string):string{
  return ""
}

function encrypt(key:string, iv:string, plain:string): string {
  const useKey = CryptoJS.enc.Utf8.parse(key);
  const useIv = CryptoJS.enc.Utf8.parse(iv);
  
  let cipherParams = CryptoJS.AES.encrypt(plain, useKey,
    {iv:useIv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, format: JsonFormatter}
  )

  const text = CryptoJS.enc.Base64.stringify(cipherParams.ciphertext)

  return text
}

function decrypt(key:string, iv:string, cipher:string){
  const useKey = CryptoJS.enc.Utf8.parse(key);
  const useIv = CryptoJS.enc.Utf8.parse(iv);

  const parsed = CryptoJS.enc.Base64.parse(cipher)
  const decoded = CryptoJS.enc.Base64.stringify(parsed)
  const data = CryptoJS.AES.decrypt(decoded, useKey, {
    iv: useIv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7
  })
  return data.toString(CryptoJS.enc.Utf8)
}

async function init(){
  let publicKey = getItem(constant.KEY_PUBLIC)
  let crypto = getItem(constant.KEY_CRYPTO)
  if(!jsEncrypt || !publicKey){
    await fetch(constant.BASE_URL+"/common/public-key", {
      method: "GET",
      headers: {"Content-Type":"application/json;charset=utf-8"}
    }).then(resp=>resp.json())
      .then(json=> {
        setItem(constant.KEY_PUBLIC, json.result)
        jsEncrypt = new JSEncrypt()
        jsEncrypt.setPublicKey(json.result)
      })
      .catch(error=>handleError(error));
  }
  if(!crypto){
    const crypto = "0987654321654321" // temp, todo update
    const iv = "1234567890123456"
    setItem(constant.KEY_CRYPTO, crypto)
    setItem(constant.KEY_IV, iv)
  }
}

function setHeaders(secretKey:string, secretIv:string, token:string|null, headers?: Headers):Headers{
  if(!headers) headers = new Headers()
  headers.set("Content-Type", "application/json;charset=utf-8")
  headers.set(constant.KEY_CRYPTO, secretKey)
  headers.set(constant.KEY_IV, secretIv)
  if(token) headers.set(constant.KEY_TOKEN, constant.TOKEN_PREFIX+token)
  return headers
}

async function get(uri:string, data?:Record<string, any>, headers?: Headers){
  await init()
  const crypto = getItem(constant.KEY_CRYPTO)!
  const iv = getItem(constant.KEY_IV)!
  const token = getItem(constant.KEY_TOKEN)
  const secretKey = jsEncrypt.encrypt(crypto) as string
  const secretIv = jsEncrypt.encrypt(iv) as string
  const text = data ? encrypt(crypto, iv, JSON.stringify(data)) : ""
  const params = encodeURIComponent(text)

  headers = setHeaders(secretKey, secretIv, token, headers)
  const url = constant.BASE_URL+uri+(params?`?params=${params}`:"")

  return fetch(url, {
    method: "GET",
    headers: headers
  }).then(resp=>resp.json())
  .then(json=>{
    if(json.ok){
      return decrypt(crypto, iv, json.result)
    }else{
      console.log(json.traceMessage)
      return `{error:${json.traceMessage}}`
    }
  })
}

async function post(uri:string, data:Record<string, any>, headers?:Headers){
  await init()
  const crypto = getItem(constant.KEY_CRYPTO)!
  const iv = getItem(constant.KEY_IV)!
  const token = getItem(constant.KEY_TOKEN)
  const secretKey = jsEncrypt.encrypt(crypto) as string
  const secretIv = jsEncrypt.encrypt(iv) as string
  const text = encrypt(crypto, iv, JSON.stringify(data))

  headers = setHeaders(secretKey, secretIv, token, headers)
  const url = constant.BASE_URL+uri

  return fetch(url, {
    method: "POST",
    headers: headers,
    body: text, 
    mode: "cors"
  }).then(resp=>resp.json())
  .then(json=>{
    if(json.ok){
      return decrypt(crypto, iv, json.result)
    }else{
      return `{error:${json.traceMessage}}`
    }
  })
}

function login(data:Record<string, any>){
  return post("/admin/user/login", data)
  .then(data=>{
    const result = JSON.parse(data)
    const permissions = JSON.stringify(result.permissionList)
    setItem(constant.KEY_TOKEN, result.token)
    setItem(constant.KEY_PERMITS, permissions)
    return true
  })
  .catch(error=>handleError(error))
}

function logout(){
  return post("/admin/user/logout", {})
  .then(data=>{
    cleanStorage()
  })
  .catch(error=>handleError(error))
}

function listUser(current:number=1,
    size:number=10,
    orders:OrderItem[]=[{column:"updateTime", asc:false}, {column:"id", asc:false}]){
  const data = {current, size, orders}
  return get("/admin/user/list", data)
  .then(data=>JSON.parse(data))
  .catch(error=>handleError(error))
}

export {
  login,
  logout,
  listUser
}